const sysInfo = wx.getSystemInfoSync()
const px = 1 / (750 / sysInfo.windowWidth)
var _fn

class css2canvas {
  constructor(ctx = _fn.mandatory()) {
    this.ctx = ctx // 初始化canvas对象

    this.drawingView = drawingView // 传入JSON配置生成配图
    this.roundRect = roundRect // 矩形/圆角区域
    this.drawImage = drawImage // 绘制图片
    this.drawPrefixText = drawPrefixText // 带标签文本（商品价格之类的）
    this.drawTag = drawTag // 绘制标签
    this.drawText = drawText // 绘制文本
    this.getWidth = getWidth // 获取文本宽度
    this.drawLine = drawLine // 绘制直线
  }
}

/**
 *
 * 根据JSON数据，绘制对应的canvas
 *
 * @param {Array} views
 * @param {String} views[item].type  数组中需要绘制的类型 必填
 * @return: Promise
 */
const drawingView = function(views = _fn.mandatory("views")) {
  return new Promise((resolve, reject) => {
    views.forEach(view => {
      view.type && typeControl[view.type] && typeControl[view.type].call(this, view)
    })
    this.ctx.draw(true) // 保留上一次的内容
    resolve()
  })
}

/**
 *
 * 绘制圆角/直角的区域
 *
 * 如果有背景，那就是填充背景的，
 * 如果只有边框配置，那么就是边框配置类型
 * 可以通过borderRadius统一配置圆角弧度，也可以单独配置，单独配置优先级更高
 * border和background一起的时候，background优先级更高
 *
 * @param {Object}  param 要绘制的具体参数
 * @param {Number}  param.x  圆角矩形选区的左上角 x坐标
 * @param {Number}  param.y  圆角矩形选区的左上角 y坐标
 * @param {Number}  param.width 圆角矩形选区的宽度
 * @param {Number}  param.height 圆角矩形选区的高度
 * @param {Number}  param.borderWidth 边框的宽度
 * @param {Number}  param.borderRadius 统一的圆弧角度
 * @param {Number}  param.radiusTop  圆角第一个角度
 * @param {Number}  param.radiusRight 圆角第二个角度
 * @param {Number}  param.radiusBottom 圆角第三个角度
 * @param {Number}  param.radiusLeft  圆角第四个角度
 * @param {Number}  param.borderColor  边框线条的颜色
 * @param {Number}  param.background 区域的背景色
 * @param {Boolean} param.clip 是否裁剪区域
 */
const roundRect = function({
  x,
  y,
  width,
  height,
  borderWidth = 0,
  borderRadius = 0,
  radiusTop = borderRadius,
  radiusRight = borderRadius,
  radiusBottom = borderRadius,
  radiusLeft = borderRadius,
  borderColor,
  background,
  clip = false
}) {
  x *= px
  y *= px
  width *= px
  height *= px
  radiusTop *= px
  radiusRight *= px
  radiusBottom *= px
  radiusLeft *= px
  borderWidth *= px

  background && (this.ctx.fillStyle = background)
  borderColor && (this.ctx.strokeStyle = borderColor)
  borderWidth && (this.ctx.lineWidth = borderWidth)
  // 开始绘制
  this.ctx.beginPath()
  // 左上角
  this.ctx.arc(x + radiusTop, y + radiusTop, radiusTop, Math.PI, Math.PI * 1.5)
  // 右上角
  this.ctx.arc(x + width - radiusRight, y + radiusRight, radiusRight, Math.PI * 1.5, Math.PI * 2)
  // 右下角
  this.ctx.arc(x + width - radiusBottom, y + height - radiusBottom, radiusBottom, 0, Math.PI * 0.5)
  // 左下角
  this.ctx.arc(x + radiusLeft, y + height - radiusLeft, radiusLeft, Math.PI * 0.5, Math.PI)
  this.ctx.closePath()

  background && this.ctx.fill() // 背景填充
  borderWidth && this.ctx.stroke() // 线条闭合
  // 剪切
  clip && this.ctx.clip()
}

/**
 * 绘制文本
 *
 * @param {Object} param
 * @param {Number} param.x x坐标
 * @param {Number} param.y y坐标
 * @param {Number} param.width 文本最大的宽度
 * @param {String} param.overflow 文本超出后的处理 [ellipsis:加省略号 | hidden:直接隐藏 | warp:超出换行]
 * @param {Number} param.maxLine 换行最大的行数
 * @param {String} param.content 需要渲染的文本
 * @param {String} param.textAlign 文本对齐方式 [ left,right,center ]
 * @param {Number} param.fontSize 文本PX
 * @param {String} param.color  文本颜色
 * @param {Number} param.fontWeight  文本加粗 [ 100:默认  200:加粗  300:更粗 ]
 * @param {Number} param.lineHeight  换行文本的行高（判断下一行开始的y坐标）
 */
const drawText = function({
  x = 0,
  y = 0,
  width,
  overflow = "ellipsis",
  maxLine = 2,
  content = "",
  textAlign = "left",
  fontSize,
  color = "#333333",
  fontWeight = 100,
  lineHeight = 1,
  baseline = "top"
}) {
  x *= px
  y *= px
  width *= px
  fontSize *= px

  color && (this.ctx.fillStyle = color)
  fontSize && (this.ctx.font = `${fontSize.toFixed(0)}px sans-serif`)

  this.ctx.setTextAlign(textAlign)

  var computeRes = _fn.computeFont.call(this, { width, content, overflow }) // 计算要显示的文本长度

  this.ctx.setTextBaseline(baseline) // 设置字体在y轴的对齐方式
  // 兼容换行的写法
  if (computeRes.draw) {
    textAlign == "center" && width && (x += width / 2) // 如果是居中文本，兼容平常习惯从左边开始
    this.ctx.fillText(computeRes.content, x, y)
    // 文本加粗范围
    fontWeight >= 200 && this.ctx.fillText(computeRes.content, x, y - 0.5)
    fontWeight >= 300 && this.ctx.fillText(computeRes.content, x - 0.5, y)
  } else if (overflow == "warp") {
    for (let i = 0; i < maxLine; i++) {
      computeRes.charNum = Number(computeRes.charNum)
      let lineContent = content.substring(i * computeRes.charNum, i * computeRes.charNum + computeRes.charNum)
      let lineY = y + i * fontSize * lineHeight

      if (i == maxLine - 1 && lineContent.length >= computeRes.charNum) {
        lineContent = lineContent.substring(0, computeRes.charNum - 2) + "..."
      }
      this.ctx.fillText(lineContent, x, lineY)
      // // 文本加粗范围
      fontWeight >= 200 && this.ctx.fillText(lineContent, x, lineY - 0.5)
      fontWeight >= 300 && this.ctx.fillText(lineContent, x - 0.5, lineY)
    }
  }
}

/**
 *
 * 绘制图片
 *
 * @param {Objct}  param 绘制对应的参数
 * @param {String} param.imgUrl 图片路径（必须存在并且可用的图片路径）
 * @param {Number} param.x x坐标
 * @param {Number} param.y y坐标
 * @param {Number} param.width 宽度
 * @param {Number} param.height 高度
 * @param {Number} param.borderRadius 统一圆角的宽度
 * @param {Number} param.radiusTop 圆角，上 弧度
 * @param {Number} param.radiusRight 圆角 右 弧度
 * @param {Number} param.radiusBottom 圆角 下 弧度
 * @param {Number} param.radiusLeft 圆角 左下 弧度
 */
const drawImage = function({
  imgUrl,
  x,
  y,
  width,
  height,
  borderRadius = 0,
  radiusTop = borderRadius,
  radiusRight = borderRadius,
  radiusBottom = borderRadius,
  radiusLeft = borderRadius
}) {
  // 备份的原像素（用于还原画布后的操作）
  var rawData = {
    x: x,
    y: y,
    width: width,
    height: height,
    borderRadius: borderRadius,
    radiusTop: radiusTop,
    radiusRight: radiusRight,
    radiusBottom: radiusBottom,
    radiusLeft: radiusLeft
  }

  // 实际计算的px像素
  x *= px
  y *= px
  width *= px
  height *= px
  borderRadius *= px
  radiusTop *= px
  radiusRight *= px
  radiusBottom *= px
  radiusLeft *= px

  // 如果需要绘制圆角矩形才调用这部分方法
  if (borderRadius != 0 || radiusTop != 0 || radiusRight != 0 || radiusBottom != 0 || radiusLeft != 0) {
    this.ctx.save() // 保存之前的内容
    roundRect.call(this, {
      x: rawData.x,
      y: rawData.y,
      width: rawData.width,
      height: rawData.height,
      borderRadius: rawData.borderRadius,
      radiusTop: rawData.radiusTop,
      radiusRight: rawData.radiusRight,
      radiusBottom: rawData.radiusBottom,
      radiusLeft: rawData.radiusLeft,
      background: "#ffffff",
      clip: true
    }) // 绘制对应的圆角
    this.ctx.drawImage(imgUrl, x, y, width, height) // 填充图片
    this.ctx.restore() // 还原画布
  } else {
    // 普通渲染图片
    this.ctx.drawImage(imgUrl, x, y, width, height) // 填充图片
  }
}

/**
 *
 * 绘制带背景的文本（tag标签）
 *
 * @param {Object} param               配置对象
 * @param {Number} param.x             x 坐标
 * @param {Number} param.y             y 坐标
 * @param {String} param.content       标签内容 默认 ""
 * @param {Number} param.fontSize      标签字体大小 默认 28
 * @param {String} param.color         标签文本颜色
 * @param {String} param.background    标签背景色
 * @param {Number} param.padding       标签 padding 距离
 * @param {Number} param.paddingTop    标签顶部 padding 距离
 * @param {Number} param.paddingRight  标签右边 padding 距离
 * @param {Number} param.paddingBottom 标签底部 padding 距离
 * @param {Number} param.paddingLeft   标签左边 padding 距离
 * @param {Number} param.borderWidth   标签描边宽度，默认为 0
 * @param {String} param.borderColor   标签描边颜色
 * @param {Number} param.borderRadius  统一圆角的宽度
 * @param {Number} param.radiusTop     圆角，上 弧度
 * @param {Number} param.radiusRight   圆角 右 弧度
 * @param {Number} param.radiusBottom  圆角 下 弧度
 * @param {Number} param.radiusLeft    圆角 左下 弧度
 */
const drawTag = function({
  x,
  y,
  content = "",
  fontSize = 28,
  color,
  background = "rgba(0, 0, 0, 0)",
  padding = 0,
  paddingTop = padding,
  paddingRight = padding,
  paddingBottom = padding,
  paddingLeft = padding,
  borderWidth = 0,
  borderColor = "rgba(0, 0, 0, 0)",
  borderRadius = 0,
  radiusTop = borderRadius,
  radiusRight = borderRadius,
  radiusBottom = borderRadius,
  radiusLeft = borderRadius
}) {
  this.ctx.save() // 保存之前的内容
  // 备份的原像素（用于还原画布后的操作）
  var rawData = {
    x,
    y,
    fontSize,
    paddingTop,
    paddingRight,
    paddingBottom,
    paddingLeft,
    borderWidth,
    radiusTop,
    radiusRight,
    radiusBottom,
    radiusLeft
  }

  x *= px
  y *= px
  fontSize *= px
  padding *= px
  paddingTop *= px
  paddingRight *= px
  paddingBottom *= px
  paddingLeft *= px
  borderWidth *= px
  borderRadius *= px
  radiusTop *= px
  radiusRight *= px
  radiusBottom *= px
  radiusLeft *= px

  // 先固定字体大小，才能测量
  this.ctx.font = `${fontSize.toFixed(0)}px sans-serif`
  // 文本宽度
  var contentWidth = getWidth.call(this, { text: content })

  var tagWidth = contentWidth + paddingLeft + paddingRight + borderWidth * 2
  // var tagHeight = (contentWidth / content.length) * 1.3 + paddingTop + paddingBottom + borderWidth * 2 // 字体高度等于字体字号*1.3
  var tagHeight = fontSize * 1.15 + paddingTop + paddingBottom + borderWidth * 2 // 字体高度等于字体字号

  // 背景/描边
  roundRect.call(this, {
    x: rawData.x,
    y: rawData.y,
    width: tagWidth / px,
    height: tagHeight / px,
    borderColor: borderWidth > 0 ? borderColor : null,
    borderWidth: borderWidth > 0 ? rawData.borderWidth : null,
    radiusTop: rawData.radiusTop,
    radiusRight: rawData.radiusRight,
    radiusBottom: rawData.radiusBottom,
    radiusLeft: rawData.radiusLeft,
    background: background,
    clip: true
  })

  // 绘制文本
  drawText.call(this, {
    x: rawData.x + rawData.paddingLeft + rawData.borderWidth,
    y: rawData.y + rawData.paddingTop + rawData.fontSize + rawData.borderWidth,
    width: tagWidth / px,
    content: content,
    color: color,
    fontSize: rawData.fontSize,
    textAlign: "left",
    baseline: "normal"
  })
  this.ctx.restore() // 还原画布
  // 返回渲染的rpx宽度和高度
  return {
    width: tagWidth / px,
    height: tagHeight / px
  }
}

/**
 *
 * 绘制带有标签的数字，比如价格，数量等
 *
 * @param {Objct}   param  参数对象
 * @param {Number}  param.x  x坐标
 * @param {Number}  param.y  y坐标
 * @param {String}  param.content  渲染的主内容
 * @param {Number}  param.width  渲染文本的宽度（用于居中等操作实现）
 * @param {String}  param.tag  需要渲染的小标
 * @param {Number}  param.fontSize 主文本的字体大小
 * @param {Number}  param.tagSize  小图标的字体大小
 * @param {String}  param.color  文本的颜色
 * @param {String}  param.textAlign  居中对齐方式
 * @param {Number}  param.fontWeight 字体加粗范围
 * @param {Boolean} param.isDel  是否有删除线
 * @param {String}  param.delColor 删除线的颜色
 * @return 返回渲染的字符的rpx长度
 */
const drawPrefixText = function({
  x,
  y,
  content = "",
  width,
  tag = "",
  fontSize = 28,
  tagSize = 20,
  color,
  textAlign = "left",
  fontWeight = 100,
  isDel = false,
  delColor
}) {
  x *= px
  y *= px
  fontSize *= px
  tagSize *= px
  width *= px

  content += "" // 转换成中文
  tag += "" // 转换成中文

  let tagWidth = getWidth.call(this, { text: tag, fontSize: tagSize, returnPx: true }) // 下标宽度
  let contentWidth = getWidth.call(this, { text: content, fontSize: fontSize, returnPx: true }) // 数字宽度
  color && (this.ctx.fillStyle = color)

  this.ctx.setTextBaseline("normal") // 设置字体基线

  // 绘制标签
  const drawTag = (x, y) => {
    tagSize && (this.ctx.font = `${tagSize.toFixed(0)}px sans-serif`)
    this.ctx.fillText(tag, x, y)
    // 文本加粗范围
    fontWeight >= 200 && this.ctx.fillText(tag, x, y - 0.5)
    fontWeight >= 300 && this.ctx.fillText(tag, x - 0.5, y)
  }

  // 绘制数组
  const drawNumber = (x, y) => {
    fontSize && (this.ctx.font = `${fontSize.toFixed(0)}px sans-serif`)
    this.ctx.fillText(content, x, y)
    // 文本加粗范围
    fontWeight >= 200 && this.ctx.fillText(content, x, y - 0.5)
    fontWeight >= 300 && this.ctx.fillText(content, x - 0.5, y)
  }

  switch (textAlign) {
    case "left":
      // 左边开始绘制，先tag，后content
      drawTag(x, y)
      drawNumber(x + tagWidth + 5, y)
      break
    case "center":
      // 中间开始绘制，先tag，后content，计算2个分别的宽度，在计算x轴宽度
      let centPoint = x + width / 2
      let textHalfWidth = x + (tagWidth + contentWidth) / 2 // 文本总长度的一半
      let tagStartPoint = centPoint - textHalfWidth // 标签开始的x坐标点
      // 左边开始绘制，先tag，后content
      drawTag(x + tagStartPoint, y)
      drawNumber(x + tagWidth + tagStartPoint + 5, y)
      break
    case "right":
      // 右边开始画，先content，后tag
      // 左边开始绘制，先tag，后content
      drawNumber(x + width - contentWidth, y)
      drawTag(x + width - contentWidth - tagSize - 5, y)
      break
  }

  // 如果需要删除线
  isDel &&
    drawLine.call(this, {
      x: x / px,
      y: (y - fontSize / 3) / px,
      end_x: (x + tagWidth + contentWidth + 15) / px,
      end_y: (y - fontSize / 3) / px,
      width: 1,
      background: delColor
    })

  return (contentWidth + tagWidth) / px
}

/**
 *
 * 获取指定文本宽度
 *
 * @param {String} text 指定文本
 * @param {Number} fontSize 指定字体大小
 * @param {Boolean} 是否返回px值 如果为false。则是返回rpx
 * @return: 返回对应的px / rpx值
 */
const getWidth = function({ text = "", fontSize, returnPx = true }) {
  fontSize *= px
  fontSize && (this.ctx.font = `${fontSize.toFixed(0)}px sans-serif`)
  return returnPx ? this.ctx.measureText(text).width : this.ctx.measureText(text).width / px
}

/**
 *
 * 绘制直线
 *
 * @param {Object} param
 * @param {Number} param.x           起始点x坐标
 * @param {Number} param.y           起始点y坐标
 * @param {Number} param.end_x       终点x坐标
 * @param {Number} param.end_y       终点y坐标
 * @param {Number} param.width       直线的宽度
 * @param {Number} param.background  直线填充的颜色
 */
const drawLine = function({ x, y, end_x = x, end_y = y, width = 1, background }) {
  x *= px
  y *= px
  end_x *= px
  end_y *= px
  width *= px

  this.ctx.setLineWidth(width)
  this.ctx.beginPath()
  this.ctx.strokeStyle = background
  this.ctx.moveTo(x, y)
  this.ctx.lineTo(end_x, end_y)
  this.ctx.stroke() // 描绘
  this.ctx.closePath()
}

/**
 *  type类型对照表
 */
const typeControl = {
  roundRect: roundRect,
  image: drawImage,
  text: drawText,
  tag: drawTag,
  prefixText: drawPrefixText,
  line: drawLine
}

_fn = {
  /**
   * 强制参数提示
   * @param {String} obj 需要必填的参数名称
   */
  mandatory: function(obj = "") {
    throw new Error(`Missing required parameters ${obj}`)
  },

  /**
   * 计算文本的高度
   * @param {Object} params
   * @param {Number} width     绘制区域的总宽度
   * @param {String} content   绘制的内容
   * @param {String} overflow  隐藏的方式
   *
   * @return {Object} result
   * @return {Boolean} result.draw 是否立即绘制文本
   * @return {Boolean} result.content 需要绘制的内容
   * @return {Boolean} result.charNum 如果不是立即绘制，返回每行多少字符
   */
  computeFont: function({ width, content, overflow }) {
    // 宽度不存在，不限制内容
    if (!width) {
      return {
        draw: true,
        content: content
      }
    }
    var textWidth = getWidth.call(this, { text: content })
    // 判断文案是否超出
    if (width && textWidth > width) {
      let charWidth = textWidth / content.length // 一个字符的宽度
      let charNum = width / (charWidth + 1) // 预留1个字符宽度，判断一行可以放下多少个字符
      charNum = charNum >= 1 ? charNum.toFixed(0) : 1

      // 直接超出隐藏
      if (overflow == "hidden") {
        return {
          draw: true,
          content: content.substring(0, charNum)
        }
      }

      // 超出加省略号
      if (overflow == "ellipsis") {
        return {
          draw: true,
          content: content.substring(0, charNum) + "..."
        }
      }

      if (overflow == "warp") {
        return {
          charNum: charNum,
          draw: false
        }
      }
    }

    return {
      draw: true,
      content: content
    }
  }
}

export default css2canvas
